Advanced Types
Topic 3: Advanced Types (Union, Intersection, Mapped, Conditional, Utility Types)
1. Problem Statement
ViBe’s Dynamic Dashboard Dilemma
ViBe’s dashboard must show:
-
Each learner’s progress (quizzes, videos, assignments, etc.)
-
Each instructor’s engagement (courses taught, feedback given)
-
Each admin’s reports (user stats, revenue, system alerts)
But the data is always changing:
-
Some learners do only quizzes, others only videos.
-
Some instructors are also learners.
-
Admins want to combine, filter, and transform data for custom reports.

The problem:
How do you build a system that can:
-
Combine different data shapes safely?
-
Handle users with multiple roles?
-
Transform or filter data for any report, without rewriting code for every new dashboard feature?
Expected outcome:
-
Flexible, type-safe code that adapts to real-world data complexity.
-
Reports and features that are easy to extend, without bugs or duplication.
2. Learning Objectives
By the end of this lesson, you’ll be able to:
-
Use union and intersection types to model flexible data.
-
Use mapped types to transform or generate new data shapes.
-
Use conditional types to make types adapt to situations.
-
Use utility types to quickly adjust existing types.
-
Build robust, future-proof code for ViBe’s dynamic needs.
3. Concept Introduction with Analogy
The Swiss Army Knife Analogy
Imagine your code is a Swiss Army knife:
-
Sometimes you need just a knife (one type).
-
Sometimes you need a knife and a screwdriver at once (combine types).
-
Sometimes you want to quickly reshape a tool for a special job (transform types).
-
Sometimes you want a tool that adapts itself based on the job (conditional types).
Advanced types let you build tools that are just as flexible and adaptable as your real-world needs.
Union Types
A union type allows a value to be one of several types.
It’s written with the |
(“or”) operator.
How They Work
-
When a value is a union, TypeScript only allows you to use members/methods that are common to all types in the union.
-
To use type-specific properties, you must narrow the type (using
typeof
,in
, or discriminant properties).
type StringOrNumber = string | number;
let value: StringOrNumber;
value = "hello"; // OK
value = 42; // OK
value = true; // Error
Intersection Types
An intersection type combines multiple types into one using the &
(“and”) operator.
How They Work
-
The resulting type has all properties of the intersected types.
-
If there’s a property with the same name but different types, TypeScript will error.
type A = { a: number };
type B = { b: string };
type AB = A & B; // { a: number; b: string }
const obj: AB = { a: 1, b: "hi" }; // OK
Mapped Types
Mapped types let you transform each property in a type, often used with keyof
and unions.
How They Work
-
You “map over” the keys of a type or union and create a new type for each key.
-
Used for making all properties optional, readonly, or changing their types.
type User = { id: string; name: string; };
type OptionalUser = { [K in keyof User]?: User[K] }; // All properties optional
Conditional Types
What Are They?
Conditional types let you choose a type based on a condition at the type level, using the form T extends U ? X : Y
.
-
TypeScript checks if the type on the left of
extends
is assignable to the one on the right. -
If true, the first branch is used; otherwise, the second.
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
4. Step-by-Step Data Modeling
Let’s model ViBe’s users and reports:
// Basic user types
type Learner = { id: string; quizzesCompleted: number };
type Instructor = { id: string; coursesTaught: number };
type Admin = { id: string; accessLevel: "basic" | "super" };
// Some users have more than one role!
type MultiRoleUser = Learner & Instructor; // Intersection: both!
type AnyUser = Learner | Instructor | Admin; // Union: one or the other
5. Live Code Walkthrough (Step-by-Step, Explained)
A. Union Types: One or the Other
function printUserInfo(user: AnyUser) {
if ("quizzesCompleted" in user) {
console.log(`Learner: ${user.quizzesCompleted} quizzes completed`);
} else if ("coursesTaught" in user) {
console.log(`Instructor: ${user.coursesTaught} courses taught`);
} else {
console.log(`Admin: Access - ${user.accessLevel}`);
}
}
- Union types let you accept different shapes and handle each safely.
B. Intersection Types: Both at Once
const alice: MultiRoleUser = {
id: "alice123",
quizzesCompleted: 10,
coursesTaught: 2
};
- Intersection types let you combine all properties for users with multiple roles.
C. Mapped Types: Transform Data Shapes
Suppose you want to track module progress for each learner:
type ModuleStatus = "not-started" | "in-progress" | "completed";
// For any list of module IDs, generate a progress map:
type ProgressMap<Modules extends string> = {
[K in Modules]: ModuleStatus;
};
type MyModules = "quiz1" | "video2" | "assignment3";
type MyProgress = ProgressMap<MyModules>;
// Result: { quiz1: ModuleStatus; video2: ModuleStatus; assignment3: ModuleStatus }
D. Conditional Types: Adapting to Data
Suppose you want to allow feedback only if a module is completed:
type FeedbackAllowed<T extends ModuleStatus> = T extends "completed" ? string : never;
type FeedbackForQuiz = FeedbackAllowed<"completed">; // string
type FeedbackForVideo = FeedbackAllowed<"in-progress">; // never
E. Utility Types: Quick Type Transformations
Suppose you want a type where all fields are optional for a draft report:
type LearnerReport = {
name: string;
score: number;
feedback: string;
};
type DraftReport = Partial<LearnerReport>; // All fields now optional
Or make a type where all fields are read-only:
type ReadonlyReport = Readonly<LearnerReport>;
6. Challenge
Your Turn!
-
Create a
type
calledInstructorOrAdmin
that can be either anInstructor
or anAdmin
. -
Given a type
Assignment = { title: string; dueDate: Date; points: number; }
, create a typeReadonlyAssignment
where none of the fields can be changed. -
Given a type
LearnerStats = { quizzes: number; videos: number; assignments: number; }
, create a typeStatsAsStrings
that has the same keys, but all values are strings.
7. Quick Recap & Key Takeaways
-
Unions: When a value can be one of several types (e.g., API responses, user roles).
-
Intersections: When you need a type that combines multiple behaviors or data sources.
-
Mapped Types: When you want to transform or generate types based on existing ones (e.g., make all fields optional).
-
Conditional Types: For type-level logic and type transformations (e.g., extracting types, enforcing rules).
-
Utility Types: For common type manipulations and code reuse.
8. (Optional) Programmer’s Workflow Checklist
-
Use union when a value can be one of many types.
-
Use intersection when you need all properties from multiple types.
-
Use mapped and utility types to quickly reshape data.
-
Use conditional types for smart, rules-based types.
-
Test your types with real data and scenarios.
9. Coming up Next
Congratulations!
You’ve mastered the Swiss Army knife of TypeScript types.
Next, imagine combining all these tools to build a reporting engine that adapts to any new content or user type ViBe invents in the future!